数据库优化:PrismaModule连接实例管理
前面对 TypeORM 的实例管理做了优化(通过 dataSourceFactory 缓存连接实例),但对 PrismaModule 而言,并没有 dataSourceFactory 这样的钩子可用。本节介绍一种更简洁的方式,直接在 Core Module 中通过静态属性管理连接实例。
连接实例管理的核心问题
在不做优化的情况下,每次调用 forRootAsync 的工厂函数时都会创建新的 PrismaClient 实例。当相同的数据库 URL 被多次请求时,会产生大量重复连接,浪费数据库资源。
方案:静态 connections 属性
在 PrismaCoreModule 中定义一个静态的 connections 属性来缓存连接实例:
export class PrismaCoreModule implements OnModuleDestroy {
// 使用 URL 作为 key 缓存连接实例
private static connections: Record<string, PrismaClient> = {};
static onApplicationShutdown() {
// 应用关闭时断开所有连接
const { connections } = PrismaCoreModule;
if (connections && Object.keys(connections).length > 0) {
for (const key of Object.keys(connections)) {
const client = connections[key];
if (client && typeof client.$disconnect === 'function') {
client.$disconnect();
}
}
}
}
}
typescript
在 forRootAsync 中复用连接
在 useFactory 工厂函数中,先检查 connections 中是否已存在对应 URL 的实例:
useFactory: async (prismaModuleOptions: PrismaModuleOptions) => {
const { datasourceUrl } = prismaModuleOptions;
// 如果该 URL 已有缓存的 Client 实例,直接复用
if (PrismaCoreModule.connections[datasourceUrl]) {
return PrismaCoreModule.connections[datasourceUrl];
}
// 创建新的 Client 实例
const client = prismaConnectionFactory(newOptions, name);
// 缓存到 connections 中
PrismaCoreModule.connections[datasourceUrl] = client;
return client;
},
typescript
连接资源的生命周期管理
// 应用关闭时自动清理
onApplicationShutdown() {
PrismaCoreModule.onApplicationShutdown();
}
typescript
验证连接复用效果
通过 PostgreSQL 的系统表查询当前活跃连接数,验证优化效果:
-- 查询当前活跃连接数
SELECT count(*) FROM pg_stat_activity;
sql
验证步骤:
- 记录启动前的连接数(例如 6)
- 启动应用并发起请求,连接数增加 1(变为 7)
- 多次发起请求,连接数保持不变(仍为 7)
- 停止应用,连接数恢复(减少 1)
优化前每次请求都会新增连接,优化后相同 URL 的请求复用已有连接。
导出 connections 供外部使用
通过创建 Provider 将 connections 注入到 DI 系统中,使外部模块可以访问连接实例:
// 定义 connections Provider
const prismaConnections: Provider = {
provide: 'PRISMA_CONNECTIONS',
useValue: PrismaCoreModule.connections,
};
// 在 forRoot 和 forRootAsync 的 return 中添加
return {
module: PrismaCoreModule,
providers: [...existingProviders, prismaConnections],
exports: [...existingExports, prismaConnections],
};
typescript
在 Controller 或 Service 中注入使用:
@Inject('PRISMA_CONNECTIONS')
private connections: Record<string, PrismaClient>;
// 根据 URL 获取特定数据库的 PrismaClient 实例
getActiveConnections() {
console.log(this.connections);
// 可基于 URL 查找对应的 Prisma 实例
}
typescript
进一步优化:控制反转
将 PrismaClient 的 class 传递到工厂函数中,实现更灵活的控制反转:
// connectionFactory 中改为接收 client class
const client = connectionFactory(newOptions, PrismaClient, name);
// 这样用户可以自定义 client 的 options 和实例化过程
// 例如使用自定义的 PrismaClient 子类
typescript
与 TypeORM 方案的对比
| 对比项 | TypeORM 方案 | Prisma 方案 |
|---|---|---|
| 缓存位置 | AppModule 的 connections 属性 | CoreModule 的静态 connections 属性 |
| 配置来源 | ConfigService + dataSourceFactory | 直接在 useFactory 中处理 |
| 生命周期管理 | 分散在多个 Service 中 | 集中在 CoreModule 的 onApplicationShutdown |
| 复杂度 | 较高(分散在多处) | 较低(集中在模块内部) |
本节总结
- 使用
static connections属性缓存 PrismaClient 实例,以datasourceUrl为 key - 在
useFactory中优先检查缓存,避免重复创建连接 - 在
onApplicationShutdown中遍历connections,逐一调用$disconnect()释放资源 - 通过 Provider 导出
connections,允许外部模块访问连接实例 - 可进一步实现控制反转,将
PrismaClientclass 作为参数传递,增加扩展灵活性
↑